package com.ylr.base.framework.aspect;

import com.ylr.base.common.constant.Constant;
import com.ylr.base.common.enums.*;
import com.ylr.base.common.exception.YlrException;
import com.ylr.base.common.po.RequestLogPo;
import com.ylr.base.common.po.user.UserPo;
import com.ylr.base.common.tool.Result;
import com.ylr.base.common.utils.HttpUtils;
import com.ylr.base.common.utils.UserUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2023-04-28 10:53:35
 * className: LogAspect 请求日志记录切面类
 * version: 1.0
 * description:
 */
@Aspect
@Component
@Order(0)
public class LogAspect {

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    private final RabbitTemplate rabbitTemplate;

    public LogAspect(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    /**
     * com.ylr.framework.controller包路径下所有类的公共接口方法作为切面
     */
    @Pointcut("execution(public * com.ylr.framework.controller..*.*(..))")
    public void logPoint() {}

    /**
     * Aop环绕通知方法
     */
    @Around("logPoint()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        HttpServletRequest request = HttpUtils.getHttpServletRequest();
        String requestURI = request.getRequestURI();
        if (requestURI.startsWith("/api")) {
            log.debug("服务间调用，无需记录日志。requestURI={}", requestURI);
            // 执行目标方法
            return pjp.proceed();
        }
        // 获取请求用户
        UserPo userPo = UserUtils.getUser();
        // 获取请求源的ip地址
        Long userId = CommonEnum.DEFAULT_ID.getLongValue();
        String username = StringUtils.EMPTY;
        if (Objects.nonNull(userPo)) {
            userId = userPo.getId();
            username = userPo.getUsername();
        }
        String fingerprint = request.getHeader(Constant.FINGERPRINT);
        String moduleCode = request.getHeader(Constant.MODULE);
        String channelCode = request.getHeader(Constant.CHANNEL);
        String device = request.getHeader(Constant.DEVICE);
        String os = request.getHeader(Constant.OS);
        String requestIp = (String) request.getAttribute(Constant.REQUEST_IP);

        Integer moduleValue = Optional.ofNullable(ModuleEnum.getModuleEnum(moduleCode))
                .map(ModuleEnum::getValue)
                .orElse(Constant.NEGATIVE_ONE);
        Integer channelValue = Optional.ofNullable(ChannelEnum.getChannelEnum(channelCode))
                .map(ChannelEnum::getValue)
                .orElse(Constant.NEGATIVE_ONE);

        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 获取目标类名称
        String className = methodSignature.getDeclaringType().getSimpleName();

        RequestLogPo requestLogPo = new RequestLogPo();
        // 设置用户主键、请求源ip地址、请求接口地址、请求接口类路径、请求接口方法名、请求类型和接口开始处理时间属性值
        requestLogPo.setUserId(userId)
                .setUsername(username)
                .setFingerprint(fingerprint)
                .setRequestIp(requestIp)
                .setModule(moduleValue)
                .setChannel(channelValue)
                .setDevice(device)
                .setOs(os)
                .setUri(requestURI)
                .setType(request.getMethod())
                .setClassName(className)
                .setMethod(methodSignature.getName())
                .setRequestId(MDC.get(Constant.TRACE_ID))
                .setStartTime(System.currentTimeMillis());

        Object result;
        try {

            // 执行目标方法
            result = pjp.proceed();

            if (result instanceof Result) {
                Result<?> _result = (Result<?>) result;
                requestLogPo.setCode(_result.getCode())
                    .setMessage(_result.getMsg());
            } else {
                requestLogPo.setCode(ResultEnum.SUCCESS.getCode())
                    .setMessage(ResultEnum.SUCCESS.getMsg());
            }
            return result;
        } catch (Throwable throwable) {

            if (throwable instanceof YlrException) {
                YlrException be = (YlrException) throwable;

                requestLogPo.setCode(be.getCode());
                requestLogPo.setMessage(be.getMessage());
            } else if (throwable instanceof ConstraintViolationException) {
                StringBuilder sb = new StringBuilder();
                for (ConstraintViolation<?> constraintViolation : ((ConstraintViolationException) throwable).getConstraintViolations()) {
                    sb.append(constraintViolation.getMessage()).append(',');
                }
                // 删除最后一个字符‘,’
                sb.delete(sb.length() - 1, sb.length());

                requestLogPo.setCode(ResultEnum.PARAM_FAIL.getCode());
                requestLogPo.setMessage(sb.toString());
                // 抛出一个新异常
                throw new ConstraintViolationException(sb.toString(), null);
            } else {
                String message = throwable.getMessage();
                if (StringUtils.isBlank(message)) {
                    message = ResultEnum.SERVICE_FAIL.getMsg();
                } else {
                    message = message.substring(0, Math.min(message.length(), 1000));
                }
                requestLogPo.setCode(ResultEnum.SERVICE_FAIL.getCode());
                requestLogPo.setMessage(message);
            }
            // 此处继续往外抛出异常，让全局异常配置可以监听到
            throw throwable;

        } finally {
            // 接口处理结束时间
            requestLogPo.setEndTime(System.currentTimeMillis());

            log.info("{} {} {} {} 耗时：{}毫秒", requestLogPo.getType(), requestLogPo.getUri(), requestLogPo.getClassName(), requestLogPo.getCode(), requestLogPo.getEndTime() - requestLogPo.getStartTime());
            if (requestLogPo.getEndTime() - requestLogPo.getStartTime() > 6000) {
                log.warn("{} {} {} 调用超过一分钟，请确认是否可以优化", requestLogPo.getUri(), requestLogPo.getClassName(), requestLogPo.getMethod());
            }

            // 发送一条消息到MQ，异步保存操作日志
            rabbitTemplate.convertAndSend(MqEnum.LOG.getExchange(), MqEnum.LOG.getRoutingKey(), requestLogPo, new CorrelationData(UUID.randomUUID().toString()));
        }
    }

}
